// SPDX-License-Identifier: GPL-2.0
/*
 * alarm.c - Lombo sio alarm driver module
 *
 * Copyright (C) 2016-2018, LomboTech Co.Ltd.
 * Author: lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <linux/device.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>

#include <mach/debug.h>

struct alarm {
	u32 irq;
	int in_num;
	int out_num;
	struct input_dev *idev;
};

struct alarm *l_alarm;

static irqreturn_t alarm_irq_handler(int irq, void *data)
{
	struct alarm *alarm = data;
	int value;

	value = __gpio_get_value(alarm->in_num);
	PRT_ERR("in_io %d input is %d\n", alarm->in_num, value);
	if (value == 1)
		input_report_key(alarm->idev, KEY_8, 1);
	else if (value == 0)
		input_report_key(alarm->idev, KEY_8, 0);

	input_sync(alarm->idev);

	return IRQ_HANDLED;
}

static int alarm_probe(struct platform_device *pdev)
{
	int ret, out_io, in_io, irq;
	struct alarm *alarm;
	struct device_node *np = pdev->dev.of_node;

	alarm = kzalloc(sizeof(struct alarm), GFP_KERNEL);
	if (alarm == NULL)
		return -EINVAL;

	in_io = of_get_named_gpio(np, "gpios", 0);
	if (in_io < 0) {
		dev_err(&pdev->dev, "gpios property not found\n");
		ret = -EINVAL;
		goto err_irq;
	}

	if (!gpio_is_valid(in_io)) {
		dev_err(&pdev->dev, "in_io %d invalid\n", in_io);
		ret = -EINVAL;
		goto err_irq;
	}
	alarm->in_num = in_io;

	ret = devm_gpio_request_one(&pdev->dev, in_io, GPIOF_IN, "alarm-in");
	if (ret < 0) {
		dev_err(&pdev->dev, "request %d fail\n", in_io);
		goto err_irq;
	}

	out_io = of_get_named_gpio(np, "gpios", 1);
	if (out_io < 0) {
		dev_err(&pdev->dev, "gpios property not found\n");
		alarm->out_num = -1;
		goto no_out_gpio;
	}

	if (!gpio_is_valid(out_io)) {
		dev_err(&pdev->dev, "gpio %d invalid\n", out_io);
		alarm->out_num = -1;
		goto no_out_gpio;
	}
	alarm->out_num = out_io;

	ret = devm_gpio_request_one(&pdev->dev, out_io,
					GPIOF_DIR_OUT, "alarm-out");
	if (ret < 0)
		dev_err(&pdev->dev, "request %d fail\n", out_io);

no_out_gpio:
	irq = platform_get_irq(pdev, 0);
	if (irq < 0) {
		dev_err(&pdev->dev, "get irq %d fail\n", irq);
		ret = -EINVAL;
		goto err_irq;
	}

	ret = request_irq(irq, alarm_irq_handler, IRQ_TYPE_EDGE_BOTH,
					"in_io-alarm-irq", (void *)alarm);
	if (unlikely(ret)) {
		dev_err(&pdev->dev, "request irq %d failed\n", irq);
		goto err_irq;
	}
	alarm->irq = irq;

	alarm->idev = input_allocate_device();
	if (alarm->idev == NULL) {
		pr_err("%s:%d, allocate input device failed\n",
			__func__,
			__LINE__);
		goto err_alloc;
	}

	set_bit(EV_KEY, alarm->idev->evbit);
	set_bit(KEY_8, alarm->idev->keybit);

	ret = input_register_device(alarm->idev);
	if (ret) {
		pr_err("%s:%d, register input device failed\n",
			__func__,
			__LINE__);
		goto err_inp;
	}

	platform_set_drvdata(pdev, (void *)alarm);
	l_alarm = alarm;
	PRT_INFO("alarm probe success\n");
	goto out;

err_inp:
	input_free_device(alarm->idev);
err_alloc:
	free_irq(irq, (void *)alarm);
err_irq:
	kfree(alarm);
out:
	return ret;
}


static int alarm_remove(struct platform_device *pdev)
{
	struct alarm *alarm = platform_get_drvdata(pdev);

	input_free_device(alarm->idev);
	free_irq(alarm->irq, (void *)alarm);
	kfree(alarm);

	return 0;
}

#ifdef CONFIG_OF
static const struct of_device_id alarm_dt_match[] = {
	{ .compatible = "lombo-alarm" },
};
MODULE_DEVICE_TABLE(of, alarm_dt_match);
#endif

static struct platform_driver alarm_driver = {
	.probe		= alarm_probe,
	.remove		= alarm_remove,
	.driver		= {
		.name	= "lombo-alarm",
		.owner	= THIS_MODULE,
	#ifdef CONFIG_OF
		.of_match_table = of_match_ptr(alarm_dt_match),
	#endif
	},
};

static ssize_t alarm_in_show(struct class *class, struct class_attribute *attr,
								char *buf)
{
	int value;

	value = __gpio_get_value(l_alarm->in_num);

	return sprintf(buf, "%d\n", value);
}
static CLASS_ATTR_RO(alarm_in);

static ssize_t alarm_out_show(struct class *class, struct class_attribute *attr,
								char *buf)
{
	int value;

	if (l_alarm->out_num == -1) {
		PRT_ERR("there is no any io config as alarm_out\n");
		value = 0;
	} else
		value = __gpio_get_value(l_alarm->out_num);

	return sprintf(buf, "%d\n", value);
}

static ssize_t alarm_out_store(struct class *class,
				struct class_attribute *attr,
				const char *buf, size_t size)
{
	int ret, output;

	ret = kstrtoint(buf, 10, &output);
	if (ret  || (( output != 0) && (output != 1)))
		PRT_ERR("set alarm_out fail, it should be 0 or 1\n");

	if (l_alarm->out_num == -1)
		PRT_ERR("there is no any io config as alarm_out\n");
	else
		gpio_set_value(l_alarm->out_num, output);

	return size;
}
static CLASS_ATTR_RW(alarm_out);

static struct attribute *alarm_attrs[] = {
	&class_attr_alarm_in.attr,
	&class_attr_alarm_out.attr,
	NULL,
};

ATTRIBUTE_GROUPS(alarm);

static struct class alarm_class = {
	.name		= "alarm",
	.owner		= THIS_MODULE,
	.class_groups	= alarm_groups,
};

static int __init alarm_driver_init(void)
{
	class_register(&alarm_class);

	return platform_driver_register(&alarm_driver);
}
module_init(alarm_driver_init);

static void __exit alarm_driver_exit(void)
{
	class_unregister(&alarm_class);
	platform_driver_unregister(&alarm_driver);
}
module_exit(alarm_driver_exit);

MODULE_DESCRIPTION("LomboTech gpadc driver");
MODULE_AUTHOR("lomboswer@lombotech.com");
MODULE_LICENSE("GPL");
